使用 linear() 函数创建令人惊叹的动画曲线
动画的魅力不仅仅在于物体的运动,同样重要的是过渡的流畅性和真实感。而实现这一目标的关键之一是使用合适的 缓动函数。在过去,我们受限于有限的缓动函数(Easing Functions),无法精细地调整动画的表现形式,限制了我们对于用户体验的创造性表达。而现在,随着 CSS 的不断发展,CSS 的缓动函数引入了 linear() 函数,它作为动画设计中的新宠儿,为 Web 设计师和开发者们提供了一种令人惊叹的工具,能够更加直观和可控的方式设计动画的变化过程。它不仅提供了更高的定制性,还让我们能够在设计中展现更多创意和个性,能够创造出更为生动、流畅和引人注目的动画效果。
在这节课中,我们将深入探讨 linear() 函数的的种种奇妙之处。我们将从基础开始,回顾 CSS 中常见的缓动函数类型,突出 linear() 函数与传统缓动函数的不同之处。然后逐步深入探讨 linear() 函数的定义、工作原理和应用,将揭示它为动画设计带来的新可能性。通过实际示例,我们将演示如何使用 linear() 函数创造更加复杂和生动的动画效果,比如模拟出弹跳、弹簧等复杂的效果,让动画更富有张力和趣味性,使 Web 应用或页面上的动画更自然和更生动。最后,我们还将介绍一些实用工具,以便 Web 设计师和开发者更好地利用 linear() 函数,提升其在动画设计中的实际价值。准备好通过学习 linear() 函数,将你的动画设计推向新的高度吧!
CSS 缓动函数的现状
我一直都认为“最好的动画是那些不被注意到的”。在制作 Web 动画效果时,其中最重要的概念之一就是使动画“感觉正确”。而实现这一目标的关键之一就是使用合适的 CSS 缓动函数。
在 CSS 中,我们使用 animation-timing-function 或 transition-timing-function 属性分别给 CSS 的帧动画和过渡动画设置缓动函数。但一直以来,CSS 都限制我们使用以下缓动函数,即 CSS 中预设置的常用缓动函数:linear (线性,也是恒速)、ease (默认缓动函数)、ease-in (渐进,也是加速)、ease-out (渐出,也是减速)、ease-in-out (渐进渐出,先加速再减速)、cubic-bezier() (三次贝塞尔曲线)和 steps() (步进缓动):
下面这个示例,展示了不同缓动函数对动画效果的影响:
这些预设的缓动函数用于控制简单的动画可能很有用,但在更复杂的动画或需要更好的控制动画速度的情况下,它们并不总是能满足我们的需求。
虽然 steps() 和 cubic-bezier() 缓动函数在控制动画运动曲线有所改善:
steps() 步进缓动函数允许我们我们将动画或过渡分解为多个片段,而不是从一个状态到另一个状态的连续过渡,使得动画的变化变得离散而非连续。cubic-bezier() 三次贝塞尔缓动函数允许我们提供了对动画缓动更多控制的能力,创建出更逼真的动画效果。甚至是通过它来模拟类似抛物线和正弦曲线,使得我们具备创建更复杂的动画能力
在介绍 cubic-bezier() 缓动函数时我说过,CSS 预设的常用缓动函数都可以通过 cubic-bezier() 函数来定义:
:root {
--linear: cubic-bezier(0, 0, 1, 1);
--ease: cubic-bezier(0.25, 0.1, 0.25, 1);
--ease-in: cubic-bezier(0.42, 0, 1, 1);
--ease-out: cubic-bezier(0, 0, 0.58, 1);
--ease-in-out: cubic-bezier(0.42, 0, 0.58, 1);
}相比而言,cubic-bezier() 函数一直以来都为创建具有一些特色的缓动提供了最大的灵活性。我们可以通过控制三次贝塞尔曲线的两个控制点坐标来获得自定义的缓动曲线。比如 easings.net 网站上提供的大部分动画曲线都可以用 cubic-bezier() 函数来定义。下面示例所呈现的就是使用 cubic-bezier() 自定义缓动函数创建的动画效果:
上面所呈现的动画效果,除了默认的预设缓动曲线之外,大部缓动曲线都来自 easings.net 网站:
如果你理解 cubic-bezier() 函数背后的数学原理,你还可以使用它来创建类似抛物线和正弦曲线,甚至将它们组合在一起创造性的定义更符合自己需求的自定义曲线。
特别声明,在《使用 cubic-bezier() 函数创建高级动画》这节课中深入探讨了
cubic-bezier()函数背后的数学原理,以及如何使用它来创建抛物线和正弦曲线,创造出更高级的动画效果。
当然,你也可以通过现代浏览器的开发者工具或 cubic-bezier.com 网站调整和可视化自定义缓动曲线,并导出结果。这意味着,即便你对三次贝塞尔缓动函数 cubic-bezier() 背后数学原理不了解,你也可以创造出令人愉悦的缓动效果。毕竟,并不是每一位 Web 设计师或开发者都对 cubic-bezier() 函数背后的数学原理感兴趣。
也就是说,如果你既不了解 cubic-bezier() 三次贝塞尔曲线背后的数学知识,又没有相关的可视化工具,那么要弄清 cubic-bezier() 函数的确切数值可能会令你感到繁琐,甚至是一种隐形的痛苦。
即使你掌握了 cubic-bezier() 缓动函数背后数学原理,或者通过相关可视化工具自定义缓动函数,但它也有其局限性。因为我们只能控制三次贝塞尔曲线的两个控制点,这意味着我们使用 cubic-bezier() 函数不可能创建所有缓动曲线,例如 easings.net 网站上的 easeInElastic 、easeOutElastic 、easeInOutElastic 、easeInBounce 、easeOutBounce 和 easeInOutBounce 等曲线无法使用 cubic-bezier() 函数创建。
也就是说,我们是无法使用现有的 CSS 缓动函数,甚至是具有高灵活性的 cubic-bezier() 函数也无法让我们创造出类似弹跳、弹簧和弹性等运动曲线(动画效果):
注,通常情况下是通过 JavaScript 或第三方库来实现弹跳,弹簧和弹性等动画效果。
当然,如果你对 @keyframes 有足够深的认识和有足够的耐心,你也可以通过一个复杂的 @keyframes 来模拟弹跳、弹簧和弹性等动画。例如下面这个模拟弹簧的动画效果:
@keyframes epic-easing-spring-custom {
0% { transform: translateX(-180px);}
1% { transform: translateX(-174.69475443775514px);}
2% { transform: translateX(-159.19014124251282px);}
3% { transform: translateX(-134.63721936300425px);}
4% { transform: translateX(-102.37856587706644px);}
5% { transform: translateX(-63.89536431040422px);}
6% { transform: translateX(-20.753944277852497px);}
7% { transform: translateX(25.446599129049844px);}
8% { transform: translateX(73.12422001618887px);}
9% { transform: translateX(120.75986108558465px);}
10% { transform: translateX(166.93841229437368px);}
11% { transform: translateX(210.38380630535664px);}
12% { transform: translateX(249.98760244816287px);}
13% { transform: translateX(284.83064856237667px);}
14% { transform: translateX(314.1976439025px);}
15% { transform: translateX(337.5846495495756px);}
16% { transform: translateX(354.6997995626464px);}
17% { transform: translateX(365.457651373545px);}
18% { transform: translateX(369.967773620661px);}
19% { transform: translateX(368.5183007071108px);}
20% { transform: translateX(361.555283887721px);}
21% { transform: translateX(349.6587377121168px);}
22% { transform: translateX(333.5163182527384px);}
23% { transform: translateX(313.89557673179195px);}
24% { transform: translateX(291.61571077245645px);}
25% { transform: translateX(267.51968810550824px);}
26% { transform: translateX(242.44754733183834px);}
27% { transform: translateX(217.2115909084839px);}
28% { transform: translateX(192.57408085110364px);}
29% { transform: translateX(169.22793187552793px);}
30% { transform: translateX(147.78077403131908px);}
31% { transform: translateX(128.74263142908978px);}
32% { transform: translateX(112.5173393529285px);}
33% { transform: translateX(99.39770250284755px);}
34% { transform: translateX(89.56428556610632px);}
35% { transform: translateX(83.08762655003488px);}
36% { transform: translateX(79.93357559326972px);}
37% { transform: translateX(79.97138903581748px);}
38% { transform: translateX(82.98415154297379px);}
39% { transform: translateX(88.68105866171732px);}
40% { transform: translateX(96.71106842366123px);}
41% { transform: translateX(106.67742307758851px);}
42% { transform: translateX(118.15254986372753px);}
43% { transform: translateX(130.69287165973202px);}
44% { transform: translateX(143.85309272993504px);}
45% { transform: translateX(157.19956982683533px);}
46% { transform: translateX(170.32243246925202px);}
47% { transform: translateX(182.84617618181136px);}
48% { transform: translateX(194.43851660894546px);}
49% { transform: translateX(204.81735852271106px);}
50% { transform: translateX(213.75579972601668px);}
51% { transform: translateX(221.085153755597px);}
52% { transform: translateX(226.69603535070922px);}
53% { transform: translateX(230.5376073459177px);}
54% { transform: translateX(232.61513570445408px);}
55% { transform: translateX(232.98603985050028px);}
56% { transform: translateX(231.75465759542152px);}
57% { transform: translateX(229.0659673893665px);}
58% { transform: translateX(225.09852525632255px);}
59% { transform: translateX(220.056879747871px);}
60% { transform: translateX(214.16372598537566px);}
61% { transform: translateX(207.65204997684083px);}
62% { transform: translateX(200.75749770242805px);}
63% { transform: translateX(193.71118091878725px);}
64% { transform: translateX(186.73310430361454px);}
65% { transform: translateX(180.02636758515058px);}
66% { transform: translateX(173.7722628453833px);}
67% { transform: translateX(168.12635241399147px);}
68% { transform: translateX(163.2155778065574px);}
69% { transform: translateX(159.13641605898374px);}
70% { transform: translateX(155.95406752720123px);}
71% { transform: translateX(153.70262959513127px);}
72% { transform: translateX(152.3861844667254px);}
73% { transform: translateX(151.98070686454793px);}
74% { transform: translateX(152.4366794185667px);}
75% { transform: translateX(153.68229004985216px);}
76% { transform: translateX(155.62707682768502px);}
77% { transform: translateX(158.1658815533279px);}
78% { transform: translateX(161.1829735141257px);}
79% { transform: translateX(164.5562091534852px);}
80% { transform: translateX(168.16110140962223px);}
81% { transform: translateX(171.87468369989745px);}
82% { transform: translateX(175.57906741653056px);}
83% { transform: translateX(179.16460775988537px);}
84% { transform: translateX(182.53261015222768px);}
85% { transform: translateX(185.59752773098705px);}
86% { transform: translateX(188.28861891592533px);}
87% { transform: translateX(190.5510522125159px);}
88% { transform: translateX(192.34646273560986px);}
89% { transform: translateX(193.65298095470297px);}
90% { transform: translateX(194.46476848623792px);}
91% { transform: translateX(194.79110807746468px);}
92% { transform: translateX(194.655105009296px);}
93% { transform: translateX(194.0920648433762px);}
94% { transform: translateX(193.14761768334898px);}
95% { transform: translateX(191.8756619217806px);}
96% { transform: translateX(190.33620088415927px);}
97% { transform: translateX(188.59314400640937px);}
98% { transform: translateX(186.7121403950091px);}
99% { transform: translateX(184.75850706786213px);}
100% { transform: translateX(182.79530714401744px);}
}
.epic-ease-spring {
animation: epic-easing-spring-custom 1500ms linear forwards:
}我们可以为元素的确切位置定义一系列关键帧,并应用线性缓动函数 linear ,因为我们所需要的类似弹簧的曲线是由这些关键帧来确定的,并不是真正的缓动函数曲线确定的。
即便是你拥有类似 EpicEasing 这样的在线工具,帮助你快速使用 @keyframes 模拟出弹跳、弹簧和弹性等动画效果,也不太容易调整和适应某些具体的需求和情境。而且还有另一个问题,我们的动画只能单向播放。如果我们想要将相同的动画反向播放,我们需要创建一个完全不同的关键帧集合。
上述这些就是 CSS 中缓动函数的当前状态。
linear() 函数创建自定义缓动函数
正如前面所述,CSS 的 cubic-bezier() 函数虽然灵活,但无法使用它创造类似弹跳、弹簧和弹性等动画效果。即使使用一个复杂的 @keyframes 可以模拟出类似的动画效果,但其灵活性和可控性对于 Web 开发者来说都不怎么友好。
庆幸的是,W3C 的缓动函数模块 Level2 (现在仍是草案阶段)为缓动函数引入了一个新的缓动函数 linear() 。它与在关键帧中将缓动效果构建到动画中不同,我们可以使用新的 linear() 函数创建完全自定义的缓动曲线,即动画效果。例如,我们可以使用 linear() 函数像下面这样创建一个弹跳、弹簧和弹性等动画效果:
:root {
/* 弹跳缓动曲线 */
--bounce-easing: linear(
0, 0.0157, 0.0625, 0.1407, 0.25 18.18%, 0.5625, 1 36.36%, 0.8907, 0.8125,
0.7657, 0.75, 0.7657, 0.8125, 0.8905, 1, 0.9532, 0.9375, 0.9531, 1, 0.9844, 1
);
/* 弹簧缓动曲线 */
--spring-easing: linear(
0, 0.009, 0.035 2.1%, 0.141, 0.281 6.7%, 0.723 12.9%, 0.938 16.7%, 1.017,
1.077, 1.121, 1.149 24.3%, 1.159, 1.163, 1.161, 1.154 29.9%, 1.129 32.8%,
1.051 39.6%, 1.017 43.1%, 0.991, 0.977 51%, 0.974 53.8%, 0.975 57.1%,
0.997 69.8%, 1.003 76.9%, 1.004 83.8%, 1
);
/* 弹性缓动曲线 */
--elastic-easing: linear(
0, 0.218 2.1%, 0.862 6.5%, 1.114, 1.296 10.7%, 1.346, 1.37 12.9%, 1.373,
1.364 14.5%, 1.315 16.2%, 1.032 21.8%, 0.941 24%, 0.891 25.9%, 0.877,
0.869 27.8%, 0.87, 0.882 30.7%, 0.907 32.4%, 0.981 36.4%, 1.012 38.3%, 1.036,
1.046 42.7% 44.1%, 1.042 45.7%, 0.996 53.3%, 0.988, 0.984 57.5%, 0.985 60.7%,
1.001 68.1%, 1.006 72.2%, 0.998 86.7%, 1
);
} 或许你和我一样,linear() 函数中那么多数字值会感到困惑和畏惧,这种想法是情有可原的,因为我们目前对 linear() 函数一无所知,仅仅知道的是,它(linear())可以允许我们高定制缓动函数,即创建完全自定义的缓动曲线(动画效果)。
接下来,我们从 linear() 的基础开始,慢慢向大家揭开 linear() 函数的神秘面纱,以及我们应该如何使用 linear() 创造高级动画,又有哪些姿势可以帮助我们快速获得 linear() 函数中的一大串神秘数字值。
linear() 函数简介
简单地说,linear() 函数定义了一个线性函数,它在其点之间线性插值,允许你创建更逼近现实动画效果的动画曲线。这意味着,使用 linear() 函数可以创建更复杂的动画,比如弹跳、弹簧和弹性等动画效果。
正如上面示例代码所示,linear() 函数接受由逗号分隔的多个停止点(点之间的插值以线性方式进行)。每个停止点都是一个从 0 到 1 的单一数字。这些数字值分布在整个时间轴上。
换句话说,这是一种以任意点数绘制图形的方法,用于定义自定义缓动曲线。这相当特殊,为我们在 CSS 动画和过渡中以前无法实现的新可能性敞开了大门。例如下面这个具有弹跳效果的缓动函数:
:root {
--bounce-easing: linear(0, 0.004, 0.016, 0.035, 0.063, 0.098, 0.141 13.6%, 0.25, 0.391, 0.563, 0.765,1, 0.891 40.9%, 0.848, 0.813, 0.785, 0.766, 0.754, 0.75, 0.754, 0.766, 0.785,0.813, 0.848, 0.891 68.2%, 1 72.7%, 0.973, 0.953, 0.941, 0.938, 0.941, 0.953,0.973, 1, 0.988, 0.984, 0.988, 1);
}要是将这个缓动函数应用到动画元素上,你可能看到类似下图这样的一个动画效果:
一个很常见的肪动动画效果,和以往不同的是,这个动画使用 linear() 函数定义的弹跳缓动曲线:
@layer animation {
@keyframes pulse {
to {
scale: .5;
}
}
.animated {
animation: pulse 2s var(--bounce-easing) infinite alternate;
}
}linear() 不等于 linear
首先要明确一点,请不要将 linear() 函数与 linear 关键字混淆。虽然 linear() 和 linear 都可以用来创建 CSS 动画和过渡的缓动曲线,但它们有明显的区别。
linear关键词是 CSS 预设的一种缓动曲线(是一条直线),指定动画元素以恒速的方式运动,在“距离时间”图上,它只有初始位置0和终点位置1linear()函数是一个分段线性函数,由多个点绘制而成的缓动曲线(即多个停止点来控制属性值的插值),在“距离时间”图上,它有N个点,至少有两个点
也就是说,当 linear() 函数的值只有 0 和 1 两个停止点时(linear(0,1)),它和 linear 关键词效果将是相同的,表示动画在整个持续时间内以恒速的方式运动。
@keyframes move {
to {
translate: 100%;
}
}
.linear-keyword {
animation: move 2s linear infinite alternate;
}
.linear-function {
animation: move 2s linear(0, 1) infinite alternate;
}不难发现,linear 和 linear(0, 1) 所呈现的动画效果是一样的。
linear() 函数的使用
正如上面示例代码所示,在 linear() 函数中,我们可以传入两个,三个,四个,甚至更多个数字值。例如:
:root {
--linear-1: linear(0.5, 1);
--linear-2: linear(0, 0.5);
--linear-3: linear(0, 0.75, 1);
--linear-4: linear(0, -0.1, 0.75, 1);
}现在的你,很想知道 linear() 函数中的数字具体含义。在具体解释它的含义之前,我们先回顾一下描述缓动曲线的“距离时间”图。
“距离时间”图分别有 x 和 y 两个轴组成,其中:
x轴代表的是动画的持续时间animation-duration或transition-durationy轴代表的是动画运行进度(或者说距离)
x 和 y 交叉绘制的线就是动画运动的速度,即缓动曲线,描述的就是缓动函数的形状。假设你有一个 move 的关键帧动画,它指定了动画元素的 translate 属性从 0 过渡到 1000px ,动画元素以恒速的运动方式从 0 移动到 1000px ,整个运动用时 1s 。使用 CSS 可以像下面这样来描述该动画:
@keyframes move {
0% {
translate: 0;
}
100% {
translate: 1000px;
}
}
.animated {
animation: move 1s linear;
}如果我们使用“距离时间”图来描述的话,那么它会像下图这样:
注,如果你想更深入的了解这方面的知识,个人建议你回过头阅读《CSS 缓动函数基础:为 Web 动画注入灵魂》。
大家需要知道的是“距离时间”图中 x 轴和 y 轴所描述的分别是动画的持续时间范围和动画进度的范围,它们的范围都是从 0 开始,到 1 结束。
有了这个认知之后,我们回到 linear() 函数的世界。linear() 函数中的数字表示的是动画停止点,该停止点同样是“距离时间”图上 x 和 y 轴交叉的坐标点。例如 linear(0, 1) 函数,它的意思是:
- 第一个停止点
0表示动画的开始点,x轴和y轴都在0点位置,相当于(0 , 0)坐标点 - 第二个停止点
1表示动画的结束点,x轴和y轴都在1点位置,相当于(1, 1)坐标点
你也可以这样理解,linear(0, 1) 只有两个状态,0 和 1 代表的是动画的进度,也就是 y 轴上的位置。它还有另一层隐含意思,0 对应的是持续时间开始位置(即 0%),而 1 对应的是持续时间结束位置(即 100%)位置。
我们再来看带有三个停止点的 linear() 函数,比如 linear(0, .75, 1) ,它的意思是,linear() 函数传入三个停止点,其数值分别为 0 、0.75 和 1 ,意味着在动画的时间周期的 50% 处,动画完成其 75% 的进度。也就是说,linear(0, .75, 1) 函数的意思是:
- 第一个停止点
0表示动画在时间周期的0%处,动画完成其进度的0%,意味着动画刚开始 - 第二个停止点
0.75表示动画在时间周期的50%处,动画完成其进度的75% - 第三个停止点
1表示动画在时间周期的100%处,动画完成其进度的100%,意味着动画已结束
因此,在动画的中间点 (持续时间的 50% 处),动画已完成了总进度的 75% ,它(linear(0, 0.75, 1))对应的“距离时间”图如下所示:
你会发现,当 linear() 函数只有两个停止点时,每个停止点对应的是动画进度的百分比,其隐含的时间轴进度分别是 0% 和 100% 。当 linear() 函数有三个停止点时,其隐含的时间轴进度分别是 0% 、50% 和 100% 。以此类推,当 linear() 函数包含四个停止点时,比如 linear(0, 0.25, 0.75, 1) ,其具体解释如下:
- 第一个停止点
0表示在时间周期的0%处,动画完成了0%的进度,意味着动画开始的时候 - 第二个停止点
0.25表示在时间周期的33.333%处,动画完成了25%的进度 - 第三个停止点
0.75表示在时间周期的66.667%处,动画完成了75%的进度 - 第四个停止点
1表示在时间周期的100%处,动画完成了100%的进度,意味着动画结束的时候
它们用于动画上,呈现的效果如下所示:
你是否发现 linear() 函数的停止点的规律:默认情况下,linear() 函数中的停止点表示的是动画完成的进度(对应着“距离时间”图中 y 轴的百分比,这个比分比值在 0% ~ 100% 之间)。对于每个停止点在时间轴(x 轴)位置,可以根据下面公式来计算:
(停止点索引号 - 1) × 1 ÷ (停止点总数 - 1) × 100%
// 停止点索引号从 1 开始我们通过具体实例来验证一下上面的公式。例如 linear(0,1) ,停止点总数是 2 ,那么相应的停止点在时间轴上的占比计算如下:
linear(0, 1)
停止点总数 = 2
第一个停止点 0 = (1 - 1)× 1 ÷ (2 - 1) × 100% = 0%
第二个停止点 1 = (2 - 1)× 1 ÷ (2 - 1) × 100% = 100%再来看 linear(0, .75, 1) 和 linear(0, 0.25, 0.75, 1) 函数中每个停止点在时间轴上的位置占比:
linear(0, .75, 1)
停止点总数 = 3
第一个停止点 0 =(1 - 1)× 1 ÷ (3 - 1) × 100% = 0%
第二个停止点 0.75 =(2 - 1)× 1 ÷ (3 - 1) × 100% = 50%
第三个停止点 1 =(3 - 1)× 1 ÷ (3 - 1) × 100% = 100%
linear(0, 0.25, 0.75, 1)
停止点总数 = 4
第一个停止点 0 =(1 - 1)× 1 ÷ (4 - 1) × 100% = 0%
第二个停止点 0.25 =(2 - 1)× 1 ÷ (4 - 1) × 100% = 33.333%
第三个停止点 0.75 =(3 - 1)× 1 ÷ (4 - 1) × 100% = 66.667%
第四个停止点 0.75 =(4 - 1)× 1 ÷ (4 - 1) × 100% = 100%你也可以换过一种方式来思考。如果 linear() 函数只有两个停止点,那么第一个停止点对应的是时间轴的 0% 处,第二个停止点对应的是时间轴 100% 处。如果 linear() 函数的停止点数量大于 2 ,它相当于在时间轴 0 ~ 1 之间切了 n-2 刀,并且切出来的线段是均分的。例如,linear() 函数的停止总数是 3 ,那么 n - 2 = 1 ,表示在时间轴上切 1 ,并且将时间轴平均分成两段。其他的依此类推:
linear() 函数的开始和停止点
使用 linear() 函数定义缓动曲线时,停止点并不一定非要从 0 到 1 。例如:
@keyframes move {
0% {
translate: 0;
}
100% {
translate: 1000px;
}
}
.animated-1 {
animation: move 1s linear(0, 1);
}
.animated-2 {
animation: move 1s linear(0, 0.5);
}
.animated-3 {
animation: move 1s linear(0.5, 1);
}虽然三个方块使用的是同一个关键帧动画 move ,动画持续时间都是 1s ,但不同的缓动函数使动画元素最终呈现的动画效果是完全不同的:
linear(0, 1)缓动函数,使动画元素(顶上红色方块)从0px(即translate: 0)移动到1000px(translate: 1000px),整个动画持续时间是1slinear(0, 0.5)缓动函数,使动画元素(中间的黄色方块)从0px(即translate: 0)移动到500px(即translate: 500px)就结束了,整个动画同样持续了1s时间。这是因为linear(0, 0.5)函数的第二个停止点0.5对应的是动画完成了50%的进度,在该示例中相当于元素只移动了500px(即50% × 1000px = 500px)。这意味着,动画元素从最初的0位置开始运动,运动到500px位置,整个动画运动就结束了linear(0.5, 1)缓动函数,使动画元素(底下的绿色方块)从500px(即translate: 500px)移动到1000px(即translate: 1000px)就结束了,整个动画同样持续了1s时间。这是因为linear(0.5, 1)函数的第一个停止点0.5对应的是动画完成了50%的进度,在该示例中相当于元素只移动了500px(即50% × 1000px = 500px)。这意味着,动画元素从最初的500px开始运动,运动到1000px位置,整个动画运动结束了
这意味着,动画可以从任意进度开始或在任意进度结束,而不会影响动画的总持续时间。
linear() 函数停止点通常是 0 ~ 1 之间的数值,但这并不是绝对的,其停止点可以是小于 0 负值,还可以是大于 1 的值。例如下面这个示例:
@keyframes move {
0% {
translate: 0;
}
100% {
translate: 1000px;
}
}
.animated-1 {
animation: move 1s linear(0, 1);
}
.animated-2 {
animation: move 1s linear(0,-.2, .5, 1);
}
.animated-3 {
animation: move 1s linear(0,1.2, .5, 1);
} 你发现了没有,虽然 move 动画在 @keyframes 中定义的 translate 属性是从 0px 到 1000px ,但 linear() 函数可以改变它的移动距离。比如:
linear(0, -.2, .5, 1)会使动画元素(黄色正方形)从0px向左移动200px,然后再向右移动500px,最后才移动1000px,它们分别对应着时间周期的0%、33.333%、66.667%和100%。换句话说,动画元素在时间周期的0%处,动画完成了进度的0%;动画元素在时间周期的33.333%处,动画元素会反向移动一段距离(约动画进度的20%);动画元素在时间周期的66.667%处,动画完成了进度的50%;动画元素在时间周期的100%处,动画完成了进度的100%。整个动画持续的时间仍然是1slinear(0, 1.2, .5, 1)会使动画元素(绿色正方形)从0px移动到1200px,然后再返回到500px,最后又移动到1000px,它们同样对应着时间周期的0%、33.333%、66.667%和100%。也就是说,动画元素在时间周期的0%处,动画完成了进度的0%;动画元素在时间周期的33.333%处,动画完成了进度的120%;动画元素在时间周期的66.667%处,动画完成了进度的50%;动画元素在时间周期的100%处,动画完成了进度的100%。整个动画持续时间同样是1s
如果用“距离时间”图来描述的话,它们大致像下图这样:
前面说过了,linear() 函数的起点的值不一定是要从 0 开始,结束点的值也不一定要从 1 结束。你可以根据需要自行调整,而且它们同样可以是比 0 小的负值和比 1 大的值。例如:
.animated-1 {
animation: move 1s linear(0, 1);
}
.animated-2 {
animation: move 1s linear(-1.2, .5, 1.2);
}
.animated-3 {
animation: move 1s linear(1.2, .5, -1.2);
} 它们对应的缓动曲线如下图所示:
这种方式使你的动画具备高可定制性,但复杂性也相应的增加了,尤其是在同一个关键帧选择器有多个属性应用的时候。比如,我们在上面示例的基础上添加旋转和缩放:
@layer animation {
@keyframes ani {
50% {
scale: 2;
}
to {
translate: calc(60vw - 1.25rem);
rotate: 360deg;
}
}
.easing::before {
animation: ani 3s var(--easing) infinite alternate;
}
}当然,这种方式用于某些情境中,可以让你创造出更引人注目的动画效果,比如下面这种渐变边框的动画效果:
@layer animation {
@property --bg-angle {
inherits: false;
initial-value: 0deg;
syntax: "<angle>";
}
@property --h {
inherits: false;
initial-value: 0;
syntax: "<number>";
}
@keyframes spin {
to {
--bg-angle: 360deg;
}
}
@keyframes hue {
to {
--h: 360;
}
}
article {
animation:
spin 2.5s infinite var(--easing),
hue 2.5s infinite var(--easing);
animation-play-state: paused;
background:
linear-gradient(
to bottom,
oklch(0.1 0.2 var(--h) / 0.95),
oklch(0.1 0.2 var(--h) / 0.95)
) padding-box,
conic-gradient(
from var(--bg-angle) in oklch longer hue,
oklch(1 0.37 calc(360 - var(--h))) 0 0
) border-box;
border: 6px solid transparent;
&:hover {
animation-play-state: running;
}
}
}指定 linear() 函数停止点的长度
默认情况下, linear() 函数停止点与时间周期占比之间的关系了。例如 linear(0, -.1, .75, 1) 函数中每个停止点在时间周期的占比分别是 0% (0 停止点),33.333% (-.1 停止点),66.667% (.75 停止点)和 100% (1 停止点)。其实,我们可以通过给停止点指定一个长度值,该长度值可以在动画的持续时间内(时间周期占比)控制停止点的位置。例如,你不希望 linear(0, -.1, .75, 1) 函数的第二个停止点 -.1 在时间周期的 33.333% 处,而是希望它位置于时间周期的 20% 处,那么我们可以在 linear() 函数的 -.1 停止点后面增加时间周期的占比,从而确切的指定停止点在时间轴上的位置。例如:
.animated-1 {
animation: move 1s linear(0,-.1, .75, 1);
}
/* 显式指定 -.1 停止点位于时间周期的 20% 处 */
.animated-2 {
animation: move 1s linear(0, -.1 20%, .75, 1);
} 我们通过绘制它们的“距离时间”图来阐述它们之间的差异:
你可能会注意到,一旦我们添加了停止点在时间周期上的位置,剩余的停止点将均匀分布在剩余的时间周期内。例如,linear(0, -.1 20%, .75, 1) ,停止点 .75 将不再在时间周期的 66.667% 处(时间周期的三分之二处),而是在 60% 处,这是因为 -.1 的停止点被指定在时间周期的 20% 处,时间周期的最后 80% 在最后三个停止点(-.1 ,.75 和 1)之间均匀分布,即最后三个停止点将剩余的 80% 时间周期均分成两段,每段 40% ,因此 .75 停止点将位于时间周期的 60% (20% + 40% = 60%)处:
我还可以为停止点指定两个时间周期的位置。例如 linear(0, -.1 20% 40%, .75, 1) 函数中的 -.1 停止点显示指定了两个时间周期的位置,其中第一个 20% 是指 -.1 停止点在时间周期的 20% 处,而且一直持续到时间周期的 40% 处。这意味着停止点 -.1 在时间周期的 20% ~ 40% 处。这个时候,时间周期剩余的 60% 在最后三个停止点(-.1 ,.75 和 1)之间均匀分布,即最后三个停止点将剩余的 60% 时间周期均分成两段,每段 30% ,因此 .75 停止点将位于时间周期的 70% (40% + 30% = 70%)处:
这种方式允许更精确地控制动画在不同时间点的进度和行为。
.animated-1 {
animation: move 1s linear(0,-.1, .75, 1);
}
/* 显式指定 -.1 停止点位于时间周期的 20% 处 */
.animated-2 {
animation: move 1s linear(0, -.1 20%, .75, 1);
}
/* 显式指定 -.1 停止点位于时间周期的 20% 处,并持续到时间周期的 40% 处 */
.animated-2 {
animation: move 1s linear(0, -.1 20% 40%, .75, 1);
} 如何使用 linear() 函数创建平滑的缓动曲线
大家可能已经留意到了,前面示例所展示的 linear() 函数绘制的“缓动曲线”和关键词 linear 绘制的线性曲线有点相似,唯一不同的是,linear() 函数绘制的是多段线性曲线,即多个直线组合而成的折线。虽然比关键词 linear 好得多,但动画缓动看起来仍然相当线性。例如 linear(0, .75, 1) 函数使得动画元素迅速移动到 0.75 的位置,然后突然切换到较慢的速度;linear(0, .2, 1) 函数使得动画元素缓慢移动到 0.2 的位置,然后突然切换到快速的速度:
但你会发现,随着 linear() 函数中的停止数量越多,它绘制出来的缓动曲线将越来越平滑。例如,如果我们想要创建更平滑的减速效果,我们需要添加更多的停止点。
.animated {
animation-timing-function: linear(0, -0.1 20%, 0.4, 0.63, 0.75, 0.84, 0.92, 0.97, 1);
}上面示例缓动函数对应的缓动曲线图:
现在,元素的减速效果虽然不是完全平滑的,但对于一个快速的动画来说,可能已经足够平滑。如果动画持续时间较长,值的变化可能更为明显。
一般来说,我们使用的停止点越多,动画就越平滑,因为停止点之间的变化将变得难以察觉。例如:
:root {
--elastic: linear(0 0%, 0.22 2.1%, 0.86 6.5%, 1.11 8.6%, 1.3 10.7%, 1.35 11.8%, 1.37 12.9%, 1.37 13.7%, 1.36 14.5%, 1.32 16.2%, 1.03 21.8%, 0.94 24%, 0.89 25.9%, 0.88 26.85%, 0.87 27.8%, 0.87 29.25%, 0.88 30.7%, 0.91 32.4%, 0.98 36.4%, 1.01 38.3%, 1.04 40.5%, 1.05 42.7%, 1.05 44.1%, 1.04 45.7%, 1 53.3%, 0.99 55.4%, 0.98 57.5%, 0.99 60.7%, 1 68.1%, 1.01 72.2%, 1 86.7%, 1 100%);
--bounce: linear(0 0%, 0 2.27%, 0.02 4.53%, 0.04 6.8%, 0.06 9.07%, 0.1 11.33%, 0.14 13.6%, 0.25 18.15%, 0.39 22.7%, 0.56 27.25%, 0.77 31.8%, 1 36.35%, 0.89 40.9%, 0.85 43.18%, 0.81 45.45%, 0.79 47.72%, 0.77 50%, 0.75 52.27%, 0.75 54.55%, 0.75 56.82%, 0.77 59.1%, 0.79 61.38%, 0.81 63.65%, 0.85 65.93%, 0.89 68.2%, 1 72.7%, 0.97 74.98%, 0.95 77.25%, 0.94 79.53%, 0.94 81.8%, 0.94 84.08%, 0.95 86.35%, 0.97 88.63%, 1 90.9%, 0.99 93.18%, 0.98 95.45%, 0.99 97.73%, 1 100%);
--emphasized: linear(0 0%, 0 1.8%, 0.01 3.6%, 0.03 6.35%, 0.07 9.1%, 0.13 11.4%, 0.19 13.4%, 0.27 15%, 0.34 16.1%, 0.54 18.35%, 0.66 20.6%, 0.72 22.4%, 0.77 24.6%, 0.81 27.3%, 0.85 30.4%, 0.88 35.1%, 0.92 40.6%, 0.94 47.2%, 0.96 55%, 0.98 64%, 0.99 74.4%, 1 86.4%, 1 100%);
}对应的缓动曲线图如下所示:
使用相同的方式,可以使用 linear() 函数绘制出更复杂的缓动曲线。例如:
:root {
--linear: linear(0, 1);
--quad-in: linear( 0, 0.0039, 0.0156, 0.0352, 0.0625, 0.0977, 0.1407, 0.1914, 0.2499, 0.3164, 0.3906 62.5%, 0.5625, 0.7656, 1 );
--quad-out: linear( 0, 0.2342, 0.4374, 0.6093 37.49%, 0.6835, 0.7499, 0.8086, 0.8593, 0.9023, 0.9375, 0.9648, 0.9844, 0.9961, 1 );
--quad-in-out: linear( 0, 0.0027, 0.0106 7.29%, 0.0425, 0.0957, 0.1701 29.16%, 0.2477, 0.3401 41.23%, 0.5982 55.18%, 0.7044 61.56%, 0.7987, 0.875 75%, 0.9297, 0.9687, 0.9922, 1 );
--power-1-in: linear( 0, 0.0039, 0.0156, 0.0352, 0.0625, 0.0977, 0.1407, 0.1914, 0.2499, 0.3164, 0.3906 62.5%, 0.5625, 0.7656, 1 );
--power-1-out: linear( 0, 0.2342, 0.4374, 0.6093 37.49%, 0.6835, 0.7499, 0.8086, 0.8593, 0.9023, 0.9375, 0.9648, 0.9844, 0.9961, 1 );
--power-1-in-out: linear( 0, 0.0027, 0.0106 7.29%, 0.0425, 0.0957, 0.1701 29.16%, 0.2477, 0.3401 41.23%, 0.5982 55.18%, 0.7044 61.56%, 0.7987, 0.875 75%, 0.9297, 0.9687, 0.9922, 1 );
--cubic-in: linear( 0, 0.0014 11.11%, 0.0071 19.24%, 0.0188 26.6%, 0.037 33.33%, 0.0634 39.87%, 0.0978 46.07%, 0.1407 52.02%, 0.1925 57.74%, 0.2559 63.49%, 0.3295 69.07%, 0.4135 74.5%, 0.5083 79.81%, 0.6141 85%, 0.7312 90.09%, 1 );
--cubic-out: linear( 0, 0.2688 9.91%, 0.3859 15%, 0.4917 20.19%, 0.5865 25.5%, 0.6705 30.93%, 0.7441 36.51%, 0.8075 42.26%, 0.8593 47.98%, 0.9022 53.93%, 0.9366 60.13%, 0.963 66.67%, 0.9812 73.4%, 0.9929 80.76%, 0.9986 88.89%, 1 );
--cubic-in-out: linear( 0, 0.0036 9.62%, 0.0185 16.66%, 0.0489 23.03%, 0.0962 28.86%, 0.1705 34.93%, 0.269 40.66%, 0.3867 45.89%, 0.5833 52.95%, 0.683 57.05%, 0.7829 62.14%, 0.8621 67.46%, 0.8991 70.68%, 0.9299 74.03%, 0.9545 77.52%, 0.9735 81.21%, 0.9865 85%, 0.9949 89.15%, 1 );
--power-2-in: linear( 0, 0.0014 11.11%, 0.0071 19.24%, 0.0188 26.6%, 0.037 33.33%, 0.0634 39.87%, 0.0978 46.07%, 0.1407 52.02%, 0.1925 57.74%, 0.2559 63.49%, 0.3295 69.07%, 0.4135 74.5%, 0.5083 79.81%, 0.6141 85%, 0.7312 90.09%, 1 );
--power-2-out: linear( 0, 0.2688 9.91%, 0.3859 15%, 0.4917 20.19%, 0.5865 25.5%, 0.6705 30.93%, 0.7441 36.51%, 0.8075 42.26%, 0.8593 47.98%, 0.9022 53.93%, 0.9366 60.13%, 0.963 66.67%, 0.9812 73.4%, 0.9929 80.76%, 0.9986 88.89%, 1 );
--power-2-in-out: linear( 0, 0.0036 9.62%, 0.0185 16.66%, 0.0489 23.03%, 0.0962 28.86%, 0.1705 34.93%, 0.269 40.66%, 0.3867 45.89%, 0.5833 52.95%, 0.683 57.05%, 0.7829 62.14%, 0.8621 67.46%, 0.8991 70.68%, 0.9299 74.03%, 0.9545 77.52%, 0.9735 81.21%, 0.9865 85%, 0.9949 89.15%, 1 );
--quart-in: linear( 0, 0.0039 25%, 0.0117 32.89%, 0.0248 39.68%, 0.0457 46.22%, 0.0743 52.21%, 0.1113 57.77%, 0.1575 63%, 0.218 68.33%, 0.2901 73.39%, 0.3745 78.23%, 0.4718 82.88%, 0.5827 87.37%, 0.7074 91.71%, 0.8462 95.91%, 1 );
--quart-out: linear( 0, 0.1538 4.09%, 0.2926 8.29%, 0.4173 12.63%, 0.5282 17.12%, 0.6255 21.77%, 0.7099 26.61%, 0.782 31.67%, 0.8425 37%, 0.8887 42.23%, 0.9257 47.79%, 0.9543 53.78%, 0.9752 60.32%, 0.9883 67.11%, 0.9961 75%, 1 );
--quart-in-out: linear( 0, 0.0029 13.8%, 0.0184 21.9%, 0.0339 25.51%, 0.0551 28.81%, 0.0827 31.88%, 0.1168 34.76%, 0.1962 39.57%, 0.3005 44.02%, 0.4084 47.53%, 0.6242 53.45%, 0.7493 57.93%, 0.8495 62.97%, 0.8888 65.67%, 0.9213 68.51%, 0.9629 73.9%, 0.9876 80.16%, 0.998 87.5%, 1 );
--power-3-in: linear( 0, 0.0039 25%, 0.0117 32.89%, 0.0248 39.68%, 0.0457 46.22%, 0.0743 52.21%, 0.1113 57.77%, 0.1575 63%, 0.218 68.33%, 0.2901 73.39%, 0.3745 78.23%, 0.4718 82.88%, 0.5827 87.37%, 0.7074 91.71%, 0.8462 95.91%, 1 );
--power-3-out: linear( 0, 0.1538 4.09%, 0.2926 8.29%, 0.4173 12.63%, 0.5282 17.12%, 0.6255 21.77%, 0.7099 26.61%, 0.782 31.67%, 0.8425 37%, 0.8887 42.23%, 0.9257 47.79%, 0.9543 53.78%, 0.9752 60.32%, 0.9883 67.11%, 0.9961 75%, 1 );
--power-3-in-out: linear( 0, 0.0029 13.8%, 0.0184 21.9%, 0.0339 25.51%, 0.0551 28.81%, 0.0827 31.88%, 0.1168 34.76%, 0.1962 39.57%, 0.3005 44.02%, 0.4084 47.53%, 0.6242 53.45%, 0.7493 57.93%, 0.8495 62.97%, 0.8888 65.67%, 0.9213 68.51%, 0.9629 73.9%, 0.9876 80.16%, 0.998 87.5%, 1 );
--quint-in: linear( 0, 0.0024 29.91%, 0.008 38.03%, 0.0179 44.72%, 0.035 51.16%, 0.0595 56.88%, 0.0922 62.08%, 0.1338 66.88%, 0.1914 71.85%, 0.262 76.5%, 0.3461 80.88%, 0.4447 85.04%, 0.5587 89.01%, 0.689 92.82%, 0.8359 96.48%, 1 );
--quint-out: linear( 0, 0.1641 3.52%, 0.311 7.18%, 0.4413 10.99%, 0.5553 14.96%, 0.6539 19.12%, 0.738 23.5%, 0.8086 28.15%, 0.8662 33.12%, 0.9078 37.92%, 0.9405 43.12%, 0.965 48.84%, 0.9821 55.28%, 0.992 61.97%, 0.9976 70.09%, 1 );
--quint-in-out: linear( 0, 0.0012 14.95%, 0.0089 22.36%, 0.0297 28.43%, 0.0668 33.43%, 0.0979 36.08%, 0.1363 38.55%, 0.2373 43.07%, 0.3675 47.01%, 0.5984 52.15%, 0.7121 55.23%, 0.8192 59.21%, 0.898 63.62%, 0.9297 66.23%, 0.9546 69.06%, 0.9733 72.17%, 0.9864 75.67%, 0.9982 83.73%, 1 );
--power-4-in: linear( 0, 0.0024 29.91%, 0.008 38.03%, 0.0179 44.72%, 0.035 51.16%, 0.0595 56.88%, 0.0922 62.08%, 0.1338 66.88%, 0.1914 71.85%, 0.262 76.5%, 0.3461 80.88%, 0.4447 85.04%, 0.5587 89.01%, 0.689 92.82%, 0.8359 96.48%, 1 );
--power-4-out: linear( 0, 0.1641 3.52%, 0.311 7.18%, 0.4413 10.99%, 0.5553 14.96%, 0.6539 19.12%, 0.738 23.5%, 0.8086 28.15%, 0.8662 33.12%, 0.9078 37.92%, 0.9405 43.12%, 0.965 48.84%, 0.9821 55.28%, 0.992 61.97%, 0.9976 70.09%, 1 );
--power-4-in-out: linear( 0, 0.0012 14.95%, 0.0089 22.36%, 0.0297 28.43%, 0.0668 33.43%, 0.0979 36.08%, 0.1363 38.55%, 0.2373 43.07%, 0.3675 47.01%, 0.5984 52.15%, 0.7121 55.23%, 0.8192 59.21%, 0.898 63.62%, 0.9297 66.23%, 0.9546 69.06%, 0.9733 72.17%, 0.9864 75.67%, 0.9982 83.73%, 1 );
--strong-in: linear( 0, 0.0024 29.91%, 0.008 38.03%, 0.0179 44.72%, 0.035 51.16%, 0.0595 56.88%, 0.0922 62.08%, 0.1338 66.88%, 0.1914 71.85%, 0.262 76.5%, 0.3461 80.88%, 0.4447 85.04%, 0.5587 89.01%, 0.689 92.82%, 0.8359 96.48%, 1 );
--strong-out: linear( 0, 0.1641 3.52%, 0.311 7.18%, 0.4413 10.99%, 0.5553 14.96%, 0.6539 19.12%, 0.738 23.5%, 0.8086 28.15%, 0.8662 33.12%, 0.9078 37.92%, 0.9405 43.12%, 0.965 48.84%, 0.9821 55.28%, 0.992 61.97%, 0.9976 70.09%, 1 );
--strong-in-out: linear( 0, 0.0012 14.95%, 0.0089 22.36%, 0.0297 28.43%, 0.0668 33.43%, 0.0979 36.08%, 0.1363 38.55%, 0.2373 43.07%, 0.3675 47.01%, 0.5984 52.15%, 0.7121 55.23%, 0.8192 59.21%, 0.898 63.62%, 0.9297 66.23%, 0.9546 69.06%, 0.9733 72.17%, 0.9864 75.67%, 0.9982 83.73%, 1 );
--elastic-in: linear( 0, 0.0019 13.34%, -0.0056 27.76%, -0.0012 31.86%, 0.0147 39.29%, 0.0161 42.46%, 0.0039 46.74%, -0.0416 54.3%, -0.046 57.29%, -0.0357, -0.0122 61.67%, 0.1176 69.29%, 0.1302 70.79%, 0.1306 72.16%, 0.1088 74.09%, 0.059 75.99%, -0.0317 78.19%, -0.3151 83.8%, -0.3643 85.52%, -0.3726, -0.3705 87.06%, -0.3463, -0.2959 89.3%, -0.1144 91.51%, 0.7822 97.9%, 1 );
--elastic-out: linear( 0, 0.2178 2.1%, 1.1144 8.49%, 1.2959 10.7%, 1.3463 11.81%, 1.3705 12.94%, 1.3726, 1.3643 14.48%, 1.3151 16.2%, 1.0317 21.81%, 0.941 24.01%, 0.8912 25.91%, 0.8694 27.84%, 0.8698 29.21%, 0.8824 30.71%, 1.0122 38.33%, 1.0357, 1.046 42.71%, 1.0416 45.7%, 0.9961 53.26%, 0.9839 57.54%, 0.9853 60.71%, 1.0012 68.14%, 1.0056 72.24%, 0.9981 86.66%, 1 );
--elastic-in-out: linear( 0, 0.0009 8.51%, -0.0047 19.22%, 0.0016 22.39%, 0.023 27.81%, 0.0237 30.08%, 0.0144 31.81%, -0.0051 33.48%, -0.1116 39.25%, -0.1181 40.59%, -0.1058 41.79%, -0.0455, 0.0701 45.34%, 0.9702 55.19%, 1.0696 56.97%, 1.0987 57.88%, 1.1146 58.82%, 1.1181 59.83%, 1.1092 60.95%, 1.0057 66.48%, 0.986 68.14%, 0.9765 69.84%, 0.9769 72.16%, 0.9984 77.61%, 1.0047 80.79%, 0.9991 91.48%, 1 );
--bounce-in: linear( 0, 0.0117, 0.0156, 0.0117, 0, 0.0273, 0.0468, 0.0586, 0.0625, 0.0586, 0.0468, 0.0273, 0 27.27%, 0.1093, 0.1875 36.36%, 0.2148, 0.2343, 0.2461, 0.25, 0.2461, 0.2344, 0.2148 52.28%, 0.1875 54.55%, 0.1095, 0, 0.2341, 0.4375, 0.6092, 0.75, 0.8593, 0.9375 90.91%, 0.9648, 0.9843, 0.9961, 1 );
--bounce-out: linear( 0, 0.0039, 0.0157, 0.0352, 0.0625 9.09%, 0.1407, 0.25, 0.3908, 0.5625, 0.7654, 1, 0.8907, 0.8125 45.45%, 0.7852, 0.7657, 0.7539, 0.75, 0.7539, 0.7657, 0.7852, 0.8125 63.64%, 0.8905, 1 72.73%, 0.9727, 0.9532, 0.9414, 0.9375, 0.9414, 0.9531, 0.9726, 1, 0.9883, 0.9844, 0.9883, 1 );
--bounce-in-out: linear( 0, 0.0078, 0, 0.0235, 0.0313, 0.0235, 0.0001 13.63%, 0.0549 15.92%, 0.0938, 0.1172, 0.125, 0.1172, 0.0939 27.26%, 0.0554 29.51%, 0.0003 31.82%, 0.2192, 0.3751 40.91%, 0.4332, 0.4734 45.8%, 0.4947 48.12%, 0.5027 51.35%, 0.5153 53.19%, 0.5437, 0.5868 57.58%, 0.6579, 0.7504 62.87%, 0.9999 68.19%, 0.9453, 0.9061, 0.8828, 0.875, 0.8828, 0.9063, 0.9451 84.08%, 0.9999 86.37%, 0.9765, 0.9688, 0.9765, 1, 0.9922, 1 );
--expo-in: linear( 0, 0.0085 31.26%, 0.0167 40.94%, 0.0289 48.86%, 0.0471 55.92%, 0.0717 61.99%, 0.1038 67.32%, 0.1443 72.07%, 0.1989 76.7%, 0.2659 80.89%, 0.3465 84.71%, 0.4419 88.22%, 0.554 91.48%, 0.6835 94.51%, 0.8316 97.34%, 1 );
--expo-out: linear( 0, 0.1684 2.66%, 0.3165 5.49%, 0.446 8.52%, 0.5581 11.78%, 0.6535 15.29%, 0.7341 19.11%, 0.8011 23.3%, 0.8557 27.93%, 0.8962 32.68%, 0.9283 38.01%, 0.9529 44.08%, 0.9711 51.14%, 0.9833 59.06%, 0.9915 68.74%, 1 );
--expo-in-out: linear( 0, 0.0053 17.18%, 0.0195 26.59%, 0.0326 30.31%, 0.0506 33.48%, 0.0744 36.25%, 0.1046 38.71%, 0.1798 42.62%, 0.2846 45.93%, 0.3991 48.37%, 0.6358 52.29%, 0.765 55.45%, 0.8622 59.3%, 0.8986 61.51%, 0.9279 63.97%, 0.9481 66.34%, 0.9641 69.01%, 0.9856 75.57%, 0.9957 84.37%, 1 );
--circ-in: linear( -0, 0.0048 9.8%, 0.0192 19.5%, 0.043 29.02%, 0.0761 38.26%, 0.1181 47.13%, 0.1685 55.56%, 0.227 63.44%, 0.2929 70.71%, 0.3656 77.3%, 0.4445 83.15%, 0.5285 88.19%, 0.6173 92.39%, 0.7099 95.7%, 0.805 98.08%, 0.9021 99.52%, 1 );
--circ-out: linear( 0, 0.0979 0.48%, 0.195 1.92%, 0.2901 4.3%, 0.3827 7.61%, 0.4715 11.81%, 0.5555 16.85%, 0.6344 22.7%, 0.7071 29.29%, 0.773 36.56%, 0.8315 44.44%, 0.8819 52.87%, 0.9239 61.74%, 0.957 70.98%, 0.9808 80.5%, 0.9952 90.2%, 1 );
--circ-in-out: linear( -0, 0.0033 5.75%, 0.0132 11.43%, 0.0296 16.95%, 0.0522 22.25%, 0.0808 27.25%, 0.1149 31.89%, 0.1542 36.11%, 0.1981 39.85%, 0.2779 44.79%, 0.3654 48.15%, 0.4422 49.66%, 0.5807 50.66%, 0.6769 53.24%, 0.7253 55.37%, 0.7714 58.01%, 0.8142 61.11%, 0.8536 64.65%, 0.9158 72.23%, 0.9619 80.87%, 0.9904 90.25%, 1 );
--sine-in: linear( 0, 0.0035, 0.0141 10.7%, 0.0318 16.09%, 0.0566 21.51%, 0.0885 26.98%, 0.1278 32.53%, 0.2288 43.93%, 0.3563 55.48%, 0.5171 67.92%, 0.7139 81.53%, 1 );
--sine-out: linear( 0, 0.2861 18.47%, 0.4829 32.08%, 0.6437 44.52%, 0.7712 56.07%, 0.8722 67.47%, 0.9115 73.02%, 0.9434 78.49%, 0.9682 83.91%, 0.9859 89.3%, 0.9965, 1 );
--sine-in-out: linear( 0, 0.007 5.35%, 0.0282 10.75%, 0.0638 16.26%, 0.1144 21.96%, 0.1833 28.16%, 0.2717 34.9%, 0.6868 62.19%, 0.775 68.54%, 0.8457 74.3%, 0.9141 81.07%, 0.9621 87.52%, 0.9905 93.8%, 1 );
--back-in: linear( 0, -0.0029 4.31%, -0.0119 9.02%, -0.0838 31.27%, -0.0956 36.64%, -0.1 41.45%, -0.0953 47.03%, -0.0792 52.25%, -0.0512 57.19%, -0.0111 61.92%, 0.0512 67.19%, 0.131 72.27%, 0.2284 77.18%, 0.3443 81.96%, 0.479 86.62%, 0.6329 91.17%, 0.8065 95.63%, 1 );
--back-out: linear( 0, 0.1935 4.37%, 0.3671 8.83%, 0.521 13.38%, 0.6557 18.04%, 0.7716 22.82%, 0.869 27.73%, 0.9488 32.81%, 1.0111 38.08%, 1.0512 42.81%, 1.0792 47.75%, 1.0953 52.97%, 1.1 58.55%, 1.0956 63.36%, 1.0838 68.73%, 1.0119 90.98%, 1.0029 95.69%, 1 );
--back-in-out: linear( 0, -0.0077 5.2%, -0.0452 16.98%, -0.0493 22.35%, -0.0418 25.57%, -0.0258 28.57%, -0.0007 31.42%, 0.0335 34.15%, 0.1242 39.03%, 0.2505 43.65%, 0.3844 47.35%, 0.656 53.68%, 0.81 58.37%, 0.9282 63.52%, 0.9719 66.23%, 1.0055 69.04%, 1.0255 71.4%, 1.0396 73.87%, 1.0477 76.48%, 1.05 79.27%, 1.0419 84.36%, 1.0059 95.49%, 1 );
--spring-1: linear(0, 0.006, 0.025 2.8%, 0.101 6.1%, 0.539 18.9%, 0.721 25.3%, 0.849 31.5%,0.937 38.1%, 0.968 41.8%, 0.991 45.7%, 1.006 50.1%, 1.015 55%, 1.017 63.9%,1.001);
--spring-2: linear(0, 0.007, 0.029 2.2%, 0.118 4.7%, 0.625 14.4%, 0.826 19%, 0.902, 0.962,1.008 26.1%, 1.041 28.7%, 1.064 32.1%, 1.07 36%, 1.061 40.5%, 1.015 53.4%,0.999 61.6%, 0.995 71.2%, 1);
--spring-3: linear(0, 0.009, 0.035 2.1%, 0.141 4.4%, 0.723 12.9%, 0.938 16.7%, 1.017, 1.077,1.121, 1.149 24.3%, 1.159, 1.163, 1.161, 1.154 29.9%, 1.129 32.8%,1.051 39.6%, 1.017 43.1%, 0.991, 0.977 51%, 0.974 53.8%, 0.975 57.1%,0.997 69.8%, 1.003 76.9%, 1);
--spring-4: linear(0, 0.009, 0.037 1.7%, 0.153 3.6%, 0.776 10.3%, 1.001, 1.142 16%, 1.185, 1.209 19%, 1.215 19.9% 20.8%, 1.199, 1.165 25%, 1.056 30.3%, 1.008 33%, 0.973,0.955 39.2%, 0.953 41.1%, 0.957 43.3%, 0.998 53.3%, 1.009 59.1% 63.7%,0.998 78.9%, 1);
--spring-5: linear(0, 0.01, 0.04 1.6%, 0.161 3.3%, 0.816 9.4%, 1.046, 1.189 14.4%, 1.231,1.254 17%, 1.259, 1.257 18.6%, 1.236, 1.194 22.3%, 1.057 27%, 0.999 29.4%,0.955 32.1%, 0.942, 0.935 34.9%, 0.933, 0.939 38.4%, 1 47.3%, 1.011,1.017 52.6%, 1.016 56.4%, 1 65.2%, 0.996 70.2%, 1.001 87.2%, 1);
--bounce-1: linear(0, 0.004, 0.016, 0.035, 0.063, 0.098, 0.141, 0.191, 0.25, 0.316, 0.391 36.8%,0.563, 0.766, 1 58.8%, 0.946, 0.908 69.1%, 0.895, 0.885, 0.879, 0.878, 0.879,0.885, 0.895, 0.908 89.7%, 0.946, 1);
--bounce-2: linear(0, 0.004, 0.016, 0.035, 0.063, 0.098, 0.141 15.1%, 0.25, 0.391, 0.562, 0.765,1, 0.892 45.2%, 0.849, 0.815, 0.788, 0.769, 0.757, 0.753, 0.757, 0.769, 0.788,0.815, 0.85, 0.892 75.2%, 1 80.2%, 0.973, 0.954, 0.943, 0.939, 0.943, 0.954,0.973, 1);
--bounce-3: linear(0, 0.004, 0.016, 0.035, 0.062, 0.098, 0.141 11.4%, 0.25, 0.39, 0.562, 0.764,1 30.3%, 0.847 34.8%, 0.787, 0.737, 0.699, 0.672, 0.655, 0.65, 0.656, 0.672,0.699, 0.738, 0.787, 0.847 61.7%, 1 66.2%, 0.946, 0.908, 0.885 74.2%, 0.879,0.878, 0.879, 0.885 79.5%, 0.908, 0.946, 1 87.4%, 0.981, 0.968, 0.96, 0.957, 0.96, 0.968, 0.981, 1);
--bounce-4: linear(0, 0.004, 0.016 3%, 0.062, 0.141, 0.25, 0.391, 0.562 18.2%, 1 24.3%, 0.81,0.676 32.3%, 0.629, 0.595, 0.575, 0.568, 0.575, 0.595, 0.629, 0.676 48.2%,0.811, 1 56.2%, 0.918, 0.86, 0.825, 0.814, 0.825, 0.86, 0.918, 1 77.2%,0.94 80.6%, 0.925, 0.92, 0.925, 0.94 87.5%, 1 90.9%, 0.974, 0.965, 0.974, 1);
--bounce-5: linear(0, 0.004, 0.016 2.5%, 0.063, 0.141, 0.25 10.1%, 0.562, 1 20.2%, 0.783, 0.627,0.534 30.9%, 0.511, 0.503, 0.511, 0.534 38%, 0.627, 0.782, 1 48.7%, 0.892,0.815, 0.769 56.3%, 0.757, 0.753, 0.757, 0.769 61.3%, 0.815, 0.892, 1 68.8%,0.908 72.4%, 0.885, 0.878, 0.885, 0.908 79.4%, 1 83%, 0.954 85.5%, 0.943,0.939, 0.943, 0.954 90.5%, 1 93%, 0.977, 0.97, 0.977, 1);
}这些缓动曲线会使你的动画更具动力感、重量感等,它们使动画与现实生活中的物体运动又更近了一层。
如何使用 linear() 创建你需要的缓动曲线
上一个示例中 linear() 函数中的一大串数字,给我的感觉,它们就像是神一样的存在,突然这些数字放入 linear() 函数,就能使我们获得更高级的缓动曲线,创造出更高级的动画。就这一堆堆神一样存在的数字,足以让你对 linear() 函数望而生畏,它的复杂度并不亚于 cubic-bezier() 。好消息是,它和 cubic-bezier() 一样,一旦你定义了一个缓动函数,你不太可能再次需要去调整它,至少有一段时间内是这样的。这基本上是一种“设定并忘记”的做法。
但我们如何首次获得这些数字呢?肯定不是人写出来的,至少我写不出来。庆幸的是,@Jake Archibald (他是 linear() 函数的缔造者)创建了一个在线生成器,为我们处理了所有繁重的工作。我们可以直接使用这个在线生成器,通过 JavaScript 或 SVG 的方式来获取 linear() 函数中的数字,并且又是我们需要的缓动曲线:
如果我们知道创建缓动曲线所需的函数,我们就可以使用 JavaScript 以较大数量的点创建平滑曲线。比如,我们可以将一些优秀的 JavaScript 库拿来使用,通过 JavaScript 定义的缓动函数转换为 linear() 函数。例如下面这个 easeOutBounce 函数。我们可以将这些缓动函数设置为 CSS 自定义属性,然后在代码中使用:
const easeOutBounce = (x) => {
const n1 = 7.5625;
const d1 = 2.75;
if (x < 1 / d1) {
return n1 * x * x;
} else if (x < 2 / d1) {
return n1 * (x -= 1.5 / d1) * x + 0.75;
} else if (x < 2.5 / d1) {
return n1 * (x -= 2.25 / d1) * x + 0.9375;
} else {
return n1 * (x -= 2.625 / d1) * x + 0.984375;
}
};
const createEase = (fn, points = 50) => {
const result = [...new Array(points)].map((d, i) => {
const x = i * (1 / points);
return fn(x);
}).join(",");
return `linear(${result})`;
};
document.body.style.setProperty("--easeOutBounce", createEase(easeOutBounce));.bounce {
animation-timing-function: var(--easeOutBounce);
}当然,你也可以将相应的 JavaScript 代码复制粘贴到 @Jake Archibald 创建的在线生成器 中,通过它来获得 linear() 函数中所需的数字。例如,前面提到的,我们无法使用 cubic-bezier() 函数创建 easings.net 网站上的 easeInElastic 、easeOutElastic 、easeInOutElastic 、easeInBounce 、easeOutBounce 和 easeInOutBounce 等曲线。现在,可以将其 JavaScript 版本的代码放入到在线生成器中,从而得到所需的 linear() 函数:
:root {
--ease-in-bounce-easing: linear(0 0% 1%, 0.01 1% 4%, 0.02 4% 5%, 0.01 6% 8%, 0 8% 9%, 0.01 10% 10%,0.02 11% 11%, 0.03 11% 12%, 0.04 12% 13%, 0.05 13% 15%, 0.06 15% 21%,0.05 21% 23%, 0.04 23% 24%, 0.03 24% 25%, 0.02 25% 26%, 0.01 26% 27%, 0, 0,0.01, 0.02, 0.02, 0.03, 0.04, 0.04, 0.05, 0.06, 0.07, 0.07, 0.08, 0.08, 0.09,0.1, 0.1, 0.11, 0.11, 0.12, 0.13, 0.13, 0.14, 0.14, 0.15, 0.15, 0.16, 0.16,0.17, 0.17, 0.18 36% 36%, 0.19, 0.19, 0.2 37% 38%, 0.21 38% 39%, 0.22 39% 40%,0.23 40% 41%, 0.24 41% 43%, 0.25 43% 48%, 0.24 48% 50%, 0.23 50% 51%,0.22 51% 52%, 0.21 52% 53%, 0.2 53% 54%, 0.19, 0.19, 0.18 55% 55%, 0.17, 0.17,0.16, 0.16, 0.15, 0.15, 0.14, 0.14, 0.13, 0.13, 0.12, 0.12, 0.11, 0.1, 0.1,0.09, 0.08, 0.08, 0.07, 0.07, 0.06, 0.05, 0.04, 0.04, 0.03, 0.02, 0.02, 0.01,0, 0.02, 0.03, 0.05, 0.06, 0.08, 0.09, 0.11, 0.12, 0.14, 0.15, 0.16, 0.18,0.19, 0.21, 0.22, 0.23, 0.25, 0.26, 0.27, 0.29, 0.3, 0.31, 0.33, 0.34, 0.35, 0.36, 0.38, 0.39, 0.4, 0.41, 0.43, 0.44, 0.45, 0.46, 0.47, 0.48, 0.49, 0.51,0.52, 0.53, 0.54, 0.55, 0.56, 0.57, 0.58, 0.59, 0.6, 0.61, 0.62, 0.63, 0.64,0.65, 0.66, 0.67, 0.67, 0.68, 0.69, 0.7, 0.71, 0.72, 0.73, 0.73, 0.74, 0.75,0.76, 0.77, 0.77, 0.78, 0.79, 0.79, 0.8, 0.81, 0.82, 0.82, 0.83, 0.83, 0.84,0.85, 0.85, 0.86, 0.86, 0.87, 0.88, 0.88, 0.89, 0.89, 0.9, 0.9, 0.91, 0.91,0.92, 0.92, 0.93 90% 91%, 0.94 91% 91%, 0.95, 0.95, 0.96 92% 93%, 0.97 93% 94%, 0.98 94% 95%, 0.99 96% 97%, 1 97% 100%);
}
.bounce {
animation-timing-function: var(--ease-in-bounce-easing);
}使用相同的方式,你可以创建一个 linear() 函数版的 easings.net 集合:
除了 easings.net 网站提供的经典缓动曲线之外,GreenSock 还提供了一些特殊的缓动曲线,如果你的动画需要使用一个特殊缓动效果时,那么它将是一个首选方案:
我可能已经猜到了,同样可以将 GreenSock 中这些特殊的缓动曲线转换成 linear() 函数。@Jhey 在 CodePen 上提供了一个转换工具,可以直接将 GreenSock 中的缓动曲线转换为 linear() 函数:
这样一来,你就可以获得一个 linear() 函数版本的 GreenSock 缓动曲线:
同样地,使用 linear() 生成器,还可以将 SVG 路径转换为 linear() 函数。如此一来,你要什么样的缓动曲线都可以实现。因为,你可以通过设计软件绘制出任何你需要的 SVG 路径,例如:
你将获得相应的 SVG 代码:
<svg width="1396" height="715" viewBox="0 0 1396 715" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.5 714C1.5 714 114.597 201.509 273.5 307.5C291.824 319.723 302.246 360.793 316.5 344C325.724 333.133 305.097 316.053 316.5 307.5C329.284 297.911 340.112 335.448 353 326C364.654 317.457 347.343 302.296 353 289C367.035 256.011 410.858 311.742 426.5 344C446.686 385.629 400.909 414.417 405 460.5C419.271 621.252 694.885 693.766 802.5 573.5C835.227 536.926 809.931 448.419 857.5 460.5C876.54 465.336 878.765 496.905 897.5 491C911.399 486.619 906.296 467.64 919 460.5C943.136 446.934 955.318 491.524 983 491C1009.63 490.496 1017.93 465.962 1044 460.5C1065.02 456.095 1081.56 473.038 1099 460.5C1123.26 443.061 1099 384 1099 384C1099 384 1098.33 337.368 1099 307.5C1101.67 188.653 1018.28 -26.1518 1133 5.00007C1199.21 22.9774 1187.44 103.766 1246 139.5C1275.68 157.613 1322.59 207.267 1328.5 173C1329.53 167.035 1327.29 163.431 1328.5 157.5C1333.91 130.954 1395.5 139.5 1395.5 139.5" stroke="black" stroke-width="3"/>
</svg>把其中 <path> 中 d 的值复制到 linear() 生成器中。不过,在此之前,你需要使用相关工具将 path 的 userSpaceOnUse 坐标转换成 objectBoundingBox 坐标:
然后把转换出来的 path 路径复制到 linear() 生成器中:
这样就你可以获得高度自定义的 linear() 函数:
你可以将 cubic-bezier() 和 linear() 函数模拟出 easings.net 网站和 GreenSock 提供的缓动曲线放在一起,从而构建一个 CSS 版本缓动曲线库:
你可以慢慢积累和沉淀,在未来的开发过程中将优秀的缓动曲线往这里添加,使得它变得越来越强大!
小结
传统上,CSS 对于动画和过渡的时间控制能力有限。以往要获得我们想要的行为,唯一的方法就是使用 JavaScript 来解决。然而,CSS 的 linear() 缓动函数的强大功能彻底了改变这一现象,现在我们可以使用 linear() 获得以往要使用 JavaScript 或第三方库才能得到的缓动曲线。
通过这节课的学习,你将学习到 linear() 函数的基础理论,并通过它来创建复杂、高级、平滑的缓动曲线,以创造更复杂的动画效果。通过一些在线工具,比如 linear() 在线生成器,你可以将 JavaScript 版本的缓动曲线转换为 linear() 函数描述的缓动曲线,甚至还可以将 SVG 路径转换为 linear() 函数的停止点。这将使得创建高级缓动曲线和动画变得容易得多。
最终,通过这节课,你可以更好地理解如何使用 linear() 函数创建自定义的、令人惊叹的动画曲线,为你的项目增添独特的视觉效果。
到这节课为止,我们从缓动函数的基础开始学习,然后进入到 cubic-bezier() 和 steps() 函数的世界,掌握了 cubic-bezier() 函数背后的数学原理,可以自定义一些复杂的缓动曲线;知道了如何 steps() 函数来创建分段动画。这节课所介绍的 linear() 函数,可以说已到了顶配级别,我们可以通过它创造任何想要的缓动曲线。从而使用它能创造出更复杂的动画效果。
最后再说一句,欲要掌握 Web 动画,必先掌握缓动曲线!